/**

This file is part of MaCI/GIMnet.

MaCI/GIMnet is free software: you can redistribute it and/or modify it 
under the terms of the GNU Lesser General Public License as published 
by the Free Software Foundation, either version 3 of the License, or 
(at your option) any later version.

MaCI/GIMnet is distributed in the hope that it will be useful, but WITHOUT 
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public 
License for more details.

You should have received a copy of the GNU Lesser General Public 
License along with GIMnet. (See COPYING.LESSER) If not, see 
<http://www.gnu.org/licenses/>.

**/
#include "gimimessagebuffer.h"

namespace gimi
{

GIMIMessageQueue::GIMIMessageQueue(const int minType,
                                   const int mSize)
  : msgQueue (),
    minorType (minType),
    maxSize (mSize)
{

}

GIMIMessageQueue::~GIMIMessageQueue()
{
  int size = msgQueue.size();

  //TODO Better way to empty a stl-queue??
  for (int i = 0; i < size; ++i) {
    GIMIMessage *ptr = msgQueue.front();
    delete ptr;
    msgQueue.pop();
  }
}

int GIMIMessageQueue::pushMessage(GIMIMessage *msg,
                                  const bool copyMessage)
{
  
  if (getCurSize() >= getMaxSize()) { // There is room in the buffer
    pop();
  }
  if (copyMessage) { // Make a complete copy of the message.
    GIMIMessage *newMsg = new GIMIMessage;
    newMsg->copy(*msg);
    msgQueue.push(newMsg);

  } else {
    msgQueue.push(msg); //FIXME Does not accept const

  }
  
  return GIMI_OK;
}

int GIMIMessageQueue::popMessage(GIMIMessage **msg)
{
  assert(msg != NULL);

  if (!msgQueue.empty()) {
    *msg = msgQueue.front();
    msgQueue.pop();
    return GIMI_OK;
  } else {
    *msg = NULL;
    return GIMI_NOMSGS;
  }
}

int GIMIMessageQueue::pop()
{
  if (!msgQueue.empty()) {
    GIMIMessage *ptr = msgQueue.front();
    delete ptr;
    msgQueue.pop();
    return GIMI_OK;
  }
  return GIMI_NOMSGS;
}

int GIMIMessageQueue::getMaxSize() const
{
  return maxSize;
}

int GIMIMessageQueue::getCurSize() const
{
  return msgQueue.size();
}

int GIMIMessageQueue::getType() const
{
  return minorType;
}

bool GIMIMessageQueue::setMaxSize(const int mSize)
{
  if (mSize < 1)
    return false;
  
  // If current size is larger than new maximum size, we should pop extra
  // messages from queue.
  int currentSize = getCurSize();
  while (currentSize > mSize) {
    assert(pop() == GIMI_OK);
    --currentSize;
  }
  maxSize = mSize;

  return true;
}

GIMIMessageMajorMap::GIMIMessageMajorMap(const int majType,
                                         const int defQueueSize)
  : msgQueueMap (),
    majorType (majType),
    messages (0),
    defaultQueueSize (defQueueSize)
{

}

GIMIMessageMajorMap::~GIMIMessageMajorMap()
{
  
}

int GIMIMessageMajorMap::pushMessage(GIMIMessage *msg,
                                     const int minorType,
                                     const bool copyMessage)
{
  t_msgQueueMapIterator qiter;
    
  qiter = msgQueueMap.find(minorType);
  if (qiter == msgQueueMap.end()) { // Did not find queue for specified minor-type.
    // Add the missing queue and get it's iterator
    qiter = (msgQueueMap.insert(t_msgQueuePair(minorType,
             GIMIMessageQueue(minorType, defaultQueueSize)))).first;
  }

  (qiter->second).pushMessage(msg, copyMessage);
  ++messages;
  
  return GIMI_OK;
}

int GIMIMessageMajorMap::popMessage(GIMIMessage **msg,
                                    const int minorType /* = GIMI_IGNOREMINORTYPE */)
{
  t_msgQueueMapIterator qiter;
  int result = GIMI_ERROR;
  
  if (messages) { // There are messages in some queue.
    if (minorType == GIMI_IGNOREMINORTYPE) { // Get message from any buffer
      for(qiter = msgQueueMap.begin(); // Loop through buffers until we get a message.
          qiter != msgQueueMap.end();
          ++qiter)
      {
        result = (qiter->second).popMessage(msg);
        if (result == GIMI_OK)
          break;
      }
    } else {
      qiter = msgQueueMap.find(minorType);
      if (qiter == msgQueueMap.end()) { // Did not find queue for specified minor-type.
        //dPrint(12, "No messages have arrived to minor-type %d.", minorType);
        return GIMI_NOMSGS;
      }
      result = (qiter->second).popMessage(msg);
    }
  } else { // No messages in this major-map, no need to check queues.
    result = GIMI_NOMSGS;
  }
  if (result == GIMI_OK) // If a message was popped, decrease messagecount.
    --messages;

  return result;
}

int GIMIMessageMajorMap::popMessageRange(GIMIMessage **msg,
                                         const int minorTypeMin,
                                         const int minorTypeMax)
{
  t_msgQueueMapIterator qiter;
  int result = GIMI_ERROR;
  
  if (messages) { // There are messages in some queue.
    if (minorTypeMin == GIMI_IGNOREMINORTYPE) { // Get message from any buffer
      for(qiter = msgQueueMap.begin(); // Loop through buffers until we get a message.
          qiter != msgQueueMap.end();
          ++qiter)
      {
        result = (qiter->second).popMessage(msg);
        if (result == GIMI_OK)
          break;
      }
    } else {
      // Find first existing queue.
      qiter = msgQueueMap.lower_bound(minorTypeMin);
      if (qiter == msgQueueMap.end() || qiter->first > minorTypeMax) { // No queues in range
        //dPrint(12, "No messages have arrived to minortype-range %d-%d.",
        //        minorTypeMin, minorTypeMax);
        return GIMI_NOMSGS;
      }
      
      // Loop through minortype-range until message found, or range/queues end.
      do {
        result = (qiter->second).popMessage(msg);
        ++qiter;
      } while (result == GIMI_NOMSGS &&
               qiter != msgQueueMap.end() &&
               qiter->first <= minorTypeMax);
    }
  } else { // No messages in this major-map, no need to check queues.
    result = GIMI_NOMSGS;
  }
  
  if (result == GIMI_OK) // If a message was popped, decrease messagecount.
    --messages;

  return result;    
}

int GIMIMessageMajorMap::getMaxSize(const int minorType) const
{
  t_msgQueueMapConstIterator qiter;
  qiter = msgQueueMap.find(minorType);
  if (qiter == msgQueueMap.end()) { // Did not find queue for specified minor-type.
    //dPrint(4, "Did not find queue for minor-type %d.", minorType);
    return defaultQueueSize;
  }

  return (qiter->second).getMaxSize();
}

int GIMIMessageMajorMap::getCurSize(const int minorType) const
{
  t_msgQueueMapConstIterator qiter;
  qiter = msgQueueMap.find(minorType);
  if (qiter == msgQueueMap.end()) { // Did not find queue for specified minor-type.
    //dPrint(4, "Did not find queue for minor-type %d.", minorType);
    return 0;
  }

  return (qiter->second).getCurSize();
}

int GIMIMessageMajorMap::getTotalSize() const
{
  return messages;
}

int GIMIMessageMajorMap::getType() const
{
  return majorType;
}

bool GIMIMessageMajorMap::setMaxSize(const int minorType,
                                     const int mSize)
{
  t_msgQueueMapIterator qiter;
    
  qiter = msgQueueMap.find(minorType);
  if (qiter == msgQueueMap.end()) { // Did not find queue for specified minor-type.
    // Add the missing queue and get it's iterator
    qiter = (msgQueueMap.insert(t_msgQueuePair(minorType,
             GIMIMessageQueue(minorType, mSize)))).first;
  }

  return (qiter->second).setMaxSize(mSize);
}

GIMIMessageBuffer::GIMIMessageBuffer(const int defQueueSize)
  : msgMajorMap (),
    messages (0),
    defaultQueueSize (defQueueSize)
{

}
    
GIMIMessageBuffer::~GIMIMessageBuffer()
{

}

int GIMIMessageBuffer::pushMessage(GIMIMessage *msg,
                                   const int majorType,
                                   const int minorType,
                                   const bool copyMessage /* = false */)
{
  t_msgMajorMapIterator miter;

  miter = msgMajorMap.find(majorType);
  if (miter == msgMajorMap.end()) { // Did not find queue for specified major-type.
    // Add the major type and get the iterator.
    miter = (msgMajorMap.insert(t_msgMajorMapPair(majorType,
             GIMIMessageMajorMap(majorType, defaultQueueSize)))).first;
    //dPrint(18, "Did not find queue for major-type %d, added it.", majorType);
  }
  
  (miter->second).pushMessage(msg, minorType, copyMessage);
  ++messages; // Increase messagecounter.

  return GIMI_OK;
}
    
int GIMIMessageBuffer::popMessage(GIMIMessage **msg,
                                  const int majorType
                                      /* = GIMI_IGNOREMAJORTYPE */,
                                  const int minorType
                                      /* = GIMI_IGNOREMINORTYPE */)
{
  int result = GIMI_ERROR;
  t_msgMajorMapIterator miter;
 
  if (messages) { // Check if there are any messages in buffer.
    // Check if majortype is set to ignore, then pop first found message.
    if (majorType == GIMI_IGNOREMAJORTYPE) { // Get message from any buffer
      for(miter = msgMajorMap.begin(); // Loop through buffers until we get a message.
          miter != msgMajorMap.end();
          ++miter)
      {
        result = (miter->second).popMessage(msg, GIMI_IGNOREMINORTYPE);
        if (result == GIMI_OK)
          break;
      }
    } else {
      miter = msgMajorMap.find(majorType);
      if (miter == msgMajorMap.end()) { // Did not find queue for specified major-type.
        //dPrint(12, "No messages have arrived to major-type %d.", majorType);
       return GIMI_NOMSGS;
      }
      result = (miter->second).popMessage(msg, minorType);
    }
  } else { // Buffer is empty, no need to check queues.
    result = GIMI_NOMSGS;
  }

  if (result == GIMI_OK) // If a message was popped, decrease messagecount
    --messages;

  return result;
}

int GIMIMessageBuffer::popMessageRange(GIMIMessage **msg,
                                       const int majorTypeMin,
                                       const int majorTypeMax,
                                       const int minorTypeMin,
                                       const int minorTypeMax)
{
  int result = GIMI_ERROR;
  t_msgMajorMapIterator miter;
  
  if (messages) { // Check if there are any messages in buffer.
    // Find first existing queue.
    miter = msgMajorMap.lower_bound(majorTypeMin);
    if (miter == msgMajorMap.end() || miter->first > majorTypeMax) { // Did not find queue from range.
      //      dPrint(12, "No messages in majortype-range %d-%d.", majorTypeMin,
      //     majorTypeMax);
      return GIMI_NOMSGS;
    }
    
    // Loop through range until message found or range ends.
    do {
      result = (miter->second).popMessageRange(msg, minorTypeMin, minorTypeMax);
      ++miter;
    } while (result == GIMI_NOMSGS &&
             miter != msgMajorMap.end() &&
             miter->first <= majorTypeMax);
  } else { // Buffer is empty, no need to check queues.
    result = GIMI_NOMSGS;
  }
  
  if (result == GIMI_OK) // If a message was popped, decrease messagecount
    --messages;

  return result;
}

int GIMIMessageBuffer::getQueueMaxSize(const int majorType,
                                       const int minorType /* = GIMI_ALLMINORTYPE */) const
{
  t_msgMajorMapConstIterator miter;
    
  miter = msgMajorMap.find(majorType);
  if (miter == msgMajorMap.end()) { // Did not find queue for specified major-type.
    //dPrint(12, "Did not find queue for major-type %d.", majorType);
    return 0;
  }

  return (miter->second).getMaxSize(minorType);
}

int GIMIMessageBuffer::getQueueCurSize(const int majorType,
                                       const int minorType /* = GIMI_ALLMINORTYPE */) const
{
  t_msgMajorMapConstIterator miter;
   
  miter = msgMajorMap.find(majorType);
  if (miter == msgMajorMap.end()) { // Did not find queue for specified major-type.
    //    dPrint(12, "Did not find queue for major-type %d.", majorType);
    return 0;
  }

  return (miter->second).getCurSize(minorType);
}

int GIMIMessageBuffer::getTotalSize() const
{
  return messages;
}

bool GIMIMessageBuffer::setQueueMaxSize(const int majorType,
                                        const int minorType,
                                        const int mSize)
{
  if (mSize < 1) {
    dPrint(ODERROR, "Cannot set queue size to < 1!");
    return false;
  }
  
  t_msgMajorMapIterator miter;

  miter = msgMajorMap.find(majorType);
  if (miter == msgMajorMap.end()) { // If queue does not exist, create it.
    miter = (msgMajorMap.insert(t_msgMajorMapPair(majorType,
             GIMIMessageMajorMap(majorType, mSize)))).first;
    //dPrint(18, "Did not find queue for major-type %d, added it.", majorType);
  }

  return (miter->second).setMaxSize(minorType, mSize);
}

void GIMIMessageBuffer::clearBuffer()
{
  msgMajorMap.clear();
  messages = 0;
}

} // end namespace gimi
